﻿// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.ComponentModel.Design;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Shell.Interop;

namespace ShaderTools.VisualStudio.LanguageServices.LanguageService
{
    internal abstract class AbstractPackage<TPackage, TLanguageService> : AbstractPackage
        where TPackage : AbstractPackage<TPackage, TLanguageService>
        where TLanguageService : AbstractLanguageService<TPackage, TLanguageService>
    {
        private TLanguageService _languageService;

        public VisualStudioWorkspace Workspace { get; private set; }

        protected AbstractPackage()
        {
        }

        protected override void Initialize()
        {
            base.Initialize();

            RegisterLanguageService(typeof(TLanguageService), () =>
            {
                // Create the language service, tell it to set itself up, then store it in a field
                // so we can notify it that it's time to clean up.
                _languageService = CreateLanguageService();
                _languageService.Setup();
                return _languageService;
            });
            var shell = (IVsShell) this.GetService(typeof(SVsShell));
            // Okay, this is also a bit strange.  We need to get our Interop dll into our process,
            // but we're in the GAC.  Ask the base Roslyn Package to load, and it will take care of
            // it for us.
            // * NOTE * workspace should never be created before loading roslyn package since roslyn package
            //          installs a service roslyn visual studio workspace requires
            shell.LoadPackage(Guids.ShaderToolsPackageId, out var setupPackage);

            this.Workspace = ComponentModel.GetService<VisualStudioWorkspace>();

            LoadComponentsInUIContextOnceSolutionFullyLoaded();
        }

        protected override void LoadComponentsInUIContext()
        {
            ForegroundObject.AssertIsForeground();
        }

        internal IComponentModel ComponentModel
        {
            get
            {
                ForegroundObject.AssertIsForeground();

                return (IComponentModel) GetService(typeof(SComponentModel));
            }
        }

        protected abstract TLanguageService CreateLanguageService();

        protected void RegisterService<T>(Func<T> serviceCreator)
        {
            ((IServiceContainer) this).AddService(typeof(T), (container, type) => serviceCreator(), promote: true);
        }

        // When registering a language service, we need to take its ComAggregate wrapper.
        protected void RegisterLanguageService(Type t, Func<object> serviceCreator)
        {
            ((IServiceContainer) this).AddService(t, (container, type) => serviceCreator(), promote: true);
        }

        protected override void Dispose(bool disposing)
        {
            // If we've created the language service then tell it it's time to clean itself up now.
            if (_languageService != null)
            {
                _languageService.TearDown();
                _languageService = null;
            }

            base.Dispose(disposing);
        }

        protected abstract string ShaderToolsLanguageName { get; }
    }
}
